#include "commands.h"

void err_msg(const char *fmt, ...);
void err_ret(const char *fmt, ...);
void err_sys(const char *fmt, ...);
void err_quit(const char *fmt, ...);
ssize_t writen(int fd, const void *ptr, size_t n);
int send_msg(int fd, int num, ...);
int close_all_fds();
char *crypt(const char *, const char *);

int check_pass(const char *plainpw, const char *cryptpw)
{
	char *cryptpasswd;
	cryptpasswd = crypt(plainpw, cryptpw);
	return (strcmp(cryptpasswd, cryptpw) == 0);
	/*return (strcmp(crypt(plainpw,cryptpw), cryptpw) == 0);*/
	
	/*return 0;*/
} 

int do_cmd_user(int fd, char *cmdline)
{
    //char username[MAXSIZE];
	strcpy(username, cmdline);
	if (strcmp(username, "anonymous") == 0) { // anonymous logs in
		islocal = 0;	
		send_msg(fd, 230);
		return 0;
	}else { // local user logs in 
		islocal = 1;	
		if (getpwnam(username) == NULL) {
			send_msg(fd, 501);
			close_all_fds();
			//err_quit("Username invalid");		
			return (-1);
		}
		send_msg(fd, 331);
		return 0;
	}			
	return 0;
}

int do_cmd_pass(int fd, char *cmdline)
{
	struct spwd *pwd;
	struct passwd *temp;

	strcpy(passwd, cmdline);	
	if ((pwd = getspnam(username)) == NULL) {
		close_all_fds();
		send_msg(fd, 530);
		err_quit("getspname error:");	
		return (-1);
	}
	if (check_pass(passwd, pwd->sp_pwdp)) {
		send_msg(fd, 230);	
		if ((temp = getpwnam(username)) == NULL) {
			err_quit("getpwnam error:");		
		}
		if (seteuid(temp->pw_uid) == -1) {
			err_quit("setuid error:");	
		}
		printf("DEBUG:process uid = %d\n", geteuid());
		return 0;
	} else {
		send_msg(fd, 503);	
        quit = 1;
		return (-1);	
	}
}

int do_cmd_syst(int fd, char *cmdline)
{
	if (send_msg(fd, 215) == -1) {
		fprintf(stderr, "syst command error!\n");	
		return -1;
	}
	return 0;
}

int do_cmd_pwd(int fd, char *cmdline)
{
	char buf[MAXPATH];	

	if (getcwd(buf, sizeof(buf)) == NULL) {
		fprintf(stderr, "getcwd:%s\n", strerror(errno));		
		printf("\n");
		return -1;
	}
	
	send_msg(fd, 257, buf);

	return 0;
}

int do_cmd_cwd(int fd, char *cmdline)
{
	if (chdir(cmdline) < 0) {
		fprintf(stderr, "chidir in cwd:%s\n", strerror(errno));	
		send_msg(fd, 550, strerror(errno));
		return (-1);
	}

	send_msg(fd, 250);

	return 0;
}

int do_cmd_delete(int fd, char *cmdline)
{
	fprintf(stdout, "Debug:process uid in delete: %d\n", geteuid());
	if (remove(cmdline) < 0) {
		fprintf(stderr, "remove in delete:%s\n", strerror(errno));	
		send_msg(fd, 550, strerror(errno));
		return (-1);
	}

	send_msg(fd, 250);
	
	return 0;
}

int get_list(const char *path, char *buf, int size)
{
	int timelen;
	char timebuf[64];
	struct tm *ptime;
	struct passwd *pwd;
	struct group *grp;
	int offsize = 0;
	//char buf[MAXSIZE];
	char pathname[MAXPATH];
	char mode[12] = "-----------";
	DIR *dp;	
	struct dirent *dirp;
	struct stat lsstat;
	struct stat pathstat;

	strcpy(pathname, path);
	if (strcmp(pathname,"") == 0) { // list current dir
		if (getcwd(pathname, MAXPATH) == NULL) {
			fprintf(stderr, "getcwd in get_list error!\n");	
			return (-1);
		}
	}
	if (lstat(pathname, &pathstat) < 0) { // get cmdline type, dir or regular file
		fprintf(stderr, "lstat in get_list error!\n");	
		return (-1);
	}
	if (S_ISDIR(pathstat.st_mode)) { // dir
		if ((dp = opendir(pathname)) == NULL) {
			fprintf(stderr, "opendir error!\n");	
			closedir(dp);
			return (-1);
		}
		offsize = 0;
		while ((dirp = readdir(dp)) != NULL) {
			for (int i = 0; i < 11; i++) 
				mode[i] = '-';	

			if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)	
				continue;
			if (lstat(dirp->d_name, &lsstat) < 0) {
				fprintf(stderr, "lstat in get_list error!\n");	
				return (-1);
			}
			if (S_ISDIR(lsstat.st_mode)) // dir 
				mode[0] = 'd';		
			if (S_ISREG(lsstat.st_mode))  // regular file
				mode[0] = '-';
			// access permission and nlinks
			if  (S_IRUSR &lsstat.st_mode)	
				mode[1] = 'r';
			if  (S_IWUSR &lsstat.st_mode)	
				mode[2] = 'w';
			if  (S_IXUSR &lsstat.st_mode)	
				mode[3] = 'x';
			if  (S_IRGRP &lsstat.st_mode)	
				mode[4] = 'r';
			if  (S_IWGRP &lsstat.st_mode)	
				mode[5] = 'w';
			if  (S_IXGRP &lsstat.st_mode)	
				mode[6] = 'x';
			if  (S_IROTH &lsstat.st_mode)	
				mode[7] = 'r';
			if  (S_IWOTH &lsstat.st_mode)	
				mode[8] = 'w';
			if  (S_IXOTH &lsstat.st_mode)	
				mode[9] = 'x';
			mode[10] = '\0';
			offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s  %d  ", mode, lsstat.st_nlink);
			// user
			if ((pwd = getpwuid(lsstat.st_uid)) == NULL) {
				fprintf(stderr, "getpwuid error in get_list!\n");	
				return (-1);
			}
			offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", pwd->pw_name);
			// group
			if ((grp = getgrgid(lsstat.st_gid)) == NULL) {
				fprintf(stderr, "getpwuid error in get_list!\n");	
				return (-1);
			}
			offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", grp->gr_name);
			// size
			offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%*d ", 10,  (int)lsstat.st_size);
			// mtime 
			ptime = localtime(&(lsstat.st_mtime));
			if (ptime && (timelen = strftime(timebuf, sizeof(timebuf), "%b %d %H:%S", ptime)) > 0) {
				timebuf[timelen] = '\0';	
				offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", timebuf);
			} else {
				fprintf(stderr, "strftime error in get_list!\n");	
				return (-1);
			}

			offsize += snprintf(buf + offsize, MAXSIZE-offsize, "%s\r\n", dirp->d_name);		
		}		
	}
	
	
	if (S_ISREG(pathstat.st_mode)) { // regular file
		offsize = 0;
		// access permission and nlinks
		if  (S_IRUSR &pathstat.st_mode)	
			mode[1] = 'r';
		if  (S_IWUSR &pathstat.st_mode)	
			mode[2] = 'w';
		if  (S_IXUSR &pathstat.st_mode)	
			mode[3] = 'x';
		if  (S_IRGRP &pathstat.st_mode)	
			mode[4] = 'r';
		if  (S_IWGRP &pathstat.st_mode)	
			mode[5] = 'w';
		if  (S_IXGRP &pathstat.st_mode)	
			mode[6] = 'x';
		if  (S_IROTH &pathstat.st_mode)	
			mode[7] = 'r';
		if  (S_IWOTH &pathstat.st_mode)	
			mode[8] = 'w';
		if  (S_IXOTH &pathstat.st_mode)	
			mode[9] = 'x';
		mode[10] = '\0';
		offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s  %d  ", mode, pathstat.st_nlink);
		// user
		if ((pwd = getpwuid(pathstat.st_uid)) == NULL) {
			fprintf(stderr, "getpwuid error in get_list!\n");	
			return (-1);
		}
		offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", pwd->pw_name);
		// group
		if ((grp = getgrgid(pathstat.st_gid)) == NULL) {
			fprintf(stderr, "getgrgid error in get_list!\n");	
			return (-1);
		}
		offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", grp->gr_name);
		// size
		offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%*d ", 10,  (int)pathstat.st_size);
		// mtime 
		ptime = localtime(&(pathstat.st_mtime));
		if (ptime && (timelen = strftime(timebuf, sizeof(timebuf), "%b %d %H:%S", ptime)) > 0) {
		timebuf[timelen] = '\0';	
		offsize += snprintf(buf+offsize, MAXSIZE-offsize, "%s ", timebuf);
		} else {
			fprintf(stderr, "strftime error in get_list!\n");	
			return (-1);
		}

		offsize += snprintf(buf + offsize, MAXSIZE-offsize, "%s\r\n", pathname);
	}

	buf[offsize] = '\0';
	return offsize;
}

int do_cmd_list(int fd, char *cmdline)
{
	char buf[MAXSIZE];
	int con_fd; 
	int n;
	if ((con_fd = get_con_fd()) <0) {
		fprintf(stderr, "get_con_fd in list:%s\n", strerror(errno));
		send_msg(fd, 425);
		return (-1);
	}
	send_msg(fd, 150, port_port);	
	if ((n = get_list(cmdline, buf, sizeof(buf))) < 0) {
		fprintf(stderr, "list command error!\n");	
		send_msg(fd, 450, strerror(errno));
		close(con_fd);
		return (-1);
	}
	if (writen(con_fd, buf, strlen(buf)) != n) {
		fprintf(stderr, "write error in do_cmd_list");	
		send_msg(fd, 450, strerror(errno));
		close(con_fd);
		return (-1);
	}
	close_all_fds();
	send_msg(fd, 226);
	
	return 0;
}

int do_cmd_quit(int fd, char *cmdline)
{
	send_msg(fd, 221);	
	quit = 1;
	return 0;
}

int do_cmd_pasv(int fd, char *cmdline)
{
	uint16_t port;
	uint32_t ip;
	int len;
	struct sockaddr_in servaddr;

	printf("pasv coming\n");

	if ((pasv_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { // TO DO: if any error here, should return error codes 
		fprintf(stderr, "socket in pasv:%s\n", strerror(errno));	
		//quit = 1;
		send_msg(fd, 550, strerror(errno));
		exit(-1);
	}
	
	len = sizeof(servaddr);
	getsockname(fd, (struct sockaddr *)(&servaddr), (socklen_t *)&len);
	servaddr.sin_port = 0; 

	if (bind(pasv_fd, (struct sockaddr *)(&servaddr), len) < 0) {
		fprintf(stderr, "bind in pasv:%s\n", strerror(errno)); 
		close(pasv_fd);	
		pasv_fd= -1;
		send_msg(fd, 550, strerror(errno));
		return -1;
	}
	
	if (listen(pasv_fd, LISTENQ) < 0){
		fprintf(stderr, "listen in pasv:%s\n", strerror(errno)); 
		close(pasv_fd);	
		pasv_fd= -1;
		send_msg(fd, 550, strerror(errno));
		return -1;
			
	} 
	getsockname(pasv_fd, (struct sockaddr *)(&servaddr), (socklen_t *)&len);
	ip = ntohl(servaddr.sin_addr.s_addr);
	port = ntohs(servaddr.sin_port);
	if (send_msg(fd, 227, (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, (ip)&0xFF,
	 		(port>>8)&0xFF,(port)&0xFF) == -1)
			return -1;
	return 0;
}

int do_cmd_mkd(int fd, char *cmdline)
{
	if (mkdir (cmdline, 0777) < 0) {
		fprintf(stderr, "mkdir in mkd:%s\n", strerror(errno));	
		send_msg(fd, 550, strerror(errno));
		return (-1);
	}	

	send_msg(fd, 250);

	return 0;
}

int do_cmd_cdup(int fd, char *cmdline)
{
	if (do_cmd_cwd(fd, cmdline) < 0) {
		fprintf(stderr, "do_cmd_cwd in cdup:%s\n", strerror(errno));	
		send_msg(fd, 550, strerror(errno));
		return (-1);
	}

	send_msg(fd, 250);

	return 0;
}

int myrmdir(const char *dir) 
{
	struct stat statbuf;
	struct dirent *dirp;
	DIR *dp;
	char filebuf[MAXPATH];


	if ((dp = opendir(dir)) == NULL) {
		fprintf(stderr, "opendir in myrmdir:%s\n", strerror(errno));	
		return (-1);
	}	

	while ((dirp = readdir(dp)) != NULL) {
		if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)	
			continue;

		strcpy(filebuf, dir);
		if (filebuf[strlen(filebuf) - 1] != '/')
			strcpy(filebuf + strlen(filebuf), "/\0");
		strcpy(filebuf + strlen(filebuf), dirp->d_name);

		if (lstat(filebuf, &statbuf) < 0) {
			fprintf(stderr, "lstat in myrmdir:%s\n", strerror(errno));	
			return (-1);
		}
		if (S_ISREG(statbuf.st_mode)) {
			if (remove(filebuf) < 0) {
				fprintf(stderr, "remove in myrmdir:%s\n", strerror(errno));	
				return (-1);
			}	
		}
		else if (S_ISDIR(statbuf.st_mode)) {
			if (myrmdir(filebuf) < 0) 
				return (-1);	
		}
	}

	if (closedir(dp) < 0) {
		fprintf(stderr, "closedir in myrmdir:%s\n", strerror(errno));
		return (-1);
	}
	if (rmdir(dir) < 0) {
		fprintf(stderr, "rmdir in myrmdir:%s\n", strerror(errno));	
		return (-1);
	}

	return 0;
}


int do_cmd_rmd(int fd, char *cmdline)
{
	char buf[MAXPATH];
	if (opendir(cmdline) == NULL) {
		if (getcwd(buf, sizeof(buf)) == NULL) {
			fprintf(stderr, "getcwd in rmd:%s\n", strerror(errno));	
			send_msg(fd, 550, strerror(errno));
			return (-1);
		}	
		strcpy(buf + strlen(buf), "/\0");
		strcpy(buf + strlen(buf), cmdline);
	} else {
		strcpy(buf, cmdline);	
	}	
	if (myrmdir(buf) < 0) {
		fprintf(stderr, "myrmdir in rmd:%s\n", strerror(errno));	
		send_msg(fd, 550, strerror(errno));
		return (-1);
	}
	
	send_msg(fd, 250);

	return 0;
}

int do_cmd_port(int fd, char *cmdline)
{
	int ip1, ip2, ip3, ip4;
	int port1, port2;
	uint32_t ip;
	//char buf[16];
	send_msg(fd, 200);

	sscanf(cmdline, "%d,%d,%d,%d,%d,%d\r\n", &ip1, &ip2, &ip3, &ip4, &port1, &port2);	
	memset(&dataip, 0, sizeof(dataip));
	ip = (ip1 << 24) + (ip2 << 16) + (ip3 << 8) + ip4;
	dataip.sin_addr.s_addr = htonl(ip);
	dataip.sin_family = AF_INET;

	port_port= (port1 <<8) + port2; 
	dataip.sin_port = htons(port_port);

	printf("PORT cmd:%s:%d\n", inet_ntoa(dataip.sin_addr), ntohs(port_port));
	fflush(stdout);
	if (port_con_fd > 0) {
		close(port_con_fd);	
		port_con_fd = -1;
	}
	if ((port_con_fd= socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		fprintf(stderr, "socket in port:%s\n", strerror(errno));	
		port_con_fd= -1;
		send_msg(fd, 421);
		return (-1);
	}
	if (connect(port_con_fd, (struct sockaddr *) &dataip, sizeof(dataip)) < 0) {
		fprintf(stderr, "connect in do_cmd_port:%s\n", strerror(errno));	
		port_con_fd = -1;
		send_msg(fd, 421);
		return (-1);
	}
	/*if ((port_con_fd= connect(port_fd, (struct sockaddr *)&dataip, sizeof(dataip))) < 0) {
		fprintf(stderr, strerror(errno));	
		return (-1);
	}*/

	return 0;
}

int do_cmd_noop(int fd, char *cmdline)
{
	send_msg(fd, 200);

	return 0;
}

int do_cmd_stor(int fd, char *cmdline)
{
	char buf[MAXSIZE];
	char filename[MAXPATH];
	int ctrl_fd;
	int filefd;
	ssize_t rtn_read;

	//get current direcotry
	/*if (getcwd(filename, sizeof(filename)) < 0) {
		fprintf(stderr, strerror(errno));	
		send_msg(fd, 500);
		return (-1);
	}*/

	strcpy(filename, cmdline);	
    //printf("get filename: %s\n", filename);

	if ((ctrl_fd = get_con_fd()) < 0) {
		fprintf(stderr, "get_con_fd:%s\n", strerror(errno));	
		send_msg(fd, 425);
		return (-1);
	}
	
	if ((filefd = open(filename, O_WRONLY | O_TRUNC), FLAGS) < 0)  {
		fprintf(stderr, "open in stor:%s\n", strerror(errno));	
		send_msg(fd, 450, strerror(errno));
		return (-1);
	}

	send_msg(fd, 150);

	alarm(data_timeout);
	timeout_type = 0; // data timeout.
	while ((rtn_read = read(ctrl_fd, buf, sizeof(buf))) > 0 || errno == EINTR) {
		if (errno == EINTR)
			continue;
		if (write(filefd, buf, rtn_read) != rtn_read) {
			fprintf(stderr, "read in stor:%s\n", strerror(errno));	
			send_msg(fd, 425);
			return (-1);
		}
	}
	close(filefd);
	close_all_fds();	
	send_msg(fd, 226);	

	return 0;
}

int do_cmd_retr(int fd, char *cmdline)
{
	//size_t nleft;
	//ssize_t nread;
	char file[MAXPATH];
	ssize_t rtn_read;
	char buf[MAXSIZE];
	struct stat statbuf;
	int connectfd;
	int filefd;

	strcpy(file, cmdline);
    printf("get file:%s\n", file);
    /*fprintf(stderr, "file name:%s\n", file);*/
	
	if (lstat(file, &statbuf) < 0) {
		fprintf(stderr, "File not exist, maybe!\n");	
		send_msg(fd, 450, strerror(errno));
		return (-1);
	}

    if (S_ISDIR(statbuf.st_mode)) {
        fprintf(stdout, "Dir\n"); 
        char tempbuf[256];
        fprintf(stderr, "MKD mm\r\n");
        if (writen(fd, tempbuf, strlen(tempbuf)) < 0) {
            fprintf(stderr, "write error:%s\n", strerror(errno)); 
            send_msg(fd, 450, strerror(errno));
            return (-1);
        }
        fprintf(stderr, "write succ\n");
        return 0;
    }
	if (! S_ISREG(statbuf.st_mode) && 
        ! S_ISDIR(statbuf.st_mode)) { // only can transfer regular file
		send_msg(fd, 450, strerror(errno));	
		return (-1);
	}
	if ((connectfd = get_con_fd()) < 0) {
		fprintf(stderr, "get_con_fd:%s\n", strerror(errno));
		send_msg(fd, 425);
		return (-1);
	}
	send_msg(fd, 150);
	if ((filefd = open(file, O_RDONLY)) < 0) {
		fprintf(stderr, "open in retr:%s\n", strerror(errno));
		close(connectfd);
		return (-1);
	}

	while ((rtn_read = read(filefd, buf, MAXSIZE)) > 0 || errno == EINTR) {
		if (errno == EINTR) 
			continue;
		if (write(connectfd, buf, rtn_read) != rtn_read) {
			fprintf(stderr, "write in retr:%s\n", strerror(errno));	
			send_msg(fd, 425);
			return (-1);
		}	
	}
	close(filefd);
	close_all_fds();
	send_msg(fd, 226);
	return 0;
}

int do_cmd_rnfr(int fd, char *cmdline)
{
	return 0;
}

int do_cmd_rnto(int fd, char *cmdline)
{
	return 0;
}

int do_cmd_type(int fd, char *cmdline) 
{
	// just send 200 command TODO
	send_msg(fd, 200);	
	
	return 0;
}

